package multi_thread.train;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Cheng Jun
 * Description: 一个线程卖票，一个线程补票，当票数为0的时候，卖票线程 等待并唤醒 补票线程进行补票——补票若干张，补票线程补票完成后，通知卖票线程
 * 注意功能解耦，卖票线程 只管卖票，补票线程只管补票
 * wait,notifyAll 实现生产者/消费者
 * @version 1.0
 * @date 2021/10/2 20:37
 */
public class TicketHall {

    private static AtomicInteger tickets = new AtomicInteger(0);
    private static Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread sellTicket = new Thread(new SellTicket(), "thread-sellTicket");
        Thread addTicket = new Thread(new AddTicket(), "thread-addTicket");
        sellTicket.start();
        Thread.sleep(1000);
        addTicket.start();
    }

    static class SellTicket implements Runnable {
        @Override
        // 卖票线程，决定是否需要补票。补票线程必须等卖票线程的通知
        public void run() {
            // 判断是否需要补票
            // 需要补票，让补票线程补票
            // 不需补票则进行卖票。卖完了呢，需要补票，补票后继续卖票，卖完了继续补票
            synchronized (LOCK) {
                // 先确保票数为0时，补一次票
                if (tickets.get() == 0) {
                    try {
                        LOCK.wait();
                        System.out.println(Thread.currentThread().getName() + " 接到通知，开始卖票！");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                while (tickets.get() > 0) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 卖出一张票，剩余票数：" + tickets.decrementAndGet());
                    if (tickets.get() == 0) {
                        // 补票
                        LOCK.notifyAll();
                        System.out.println(Thread.currentThread().getName() + " 发出通知，及时补票");
                        try {
                            LOCK.wait();
                            System.out.println(Thread.currentThread().getName() + " 接到通知，开始卖票！");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    static class AddTicket implements Runnable {
        @Override
        // 补票线程，补完票 需要通知卖票线程，然后继续等待被唤醒
        public void run() {
            // 补票
            // 补完后，通知卖票
            synchronized (LOCK) {
                while (tickets.get() == 0) {
                    System.out.println(Thread.currentThread().getName() + " 开始补票");
                    tickets.addAndGet(2);
                    System.out.println(Thread.currentThread().getName() + " 补票完成!");
                    // 注意必须先通知，再 调用 wait 方法，因为如果先wait 就会释放锁，后面的代码不能执行
                    LOCK.notifyAll();
                    try {
                        System.out.println(Thread.currentThread().getName() + " 发出通知，请及时取票");
                        LOCK.wait();
                        System.out.println(Thread.currentThread().getName() +  " 票数为 " + tickets.get());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
